/*
 *             Copyright (C) 2024 Steve Jrong
 * 
 * 	   GitHub Homepage: https://www.github.com/SteveJrong
 *      Gitee Homepage: https://gitee.com/stevejrong1024
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.stevejrong.waiter.aa.component.infrastructure.aop;

import java.text.ParseException;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import com.google.common.base.Splitter;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.stevejrong.waiter.aa.component.infrastructure.util.RSAUtil;
import com.stevejrong.waiter.aa.component.infrastructure.util.ReactiveHttpExchangeUtil;
import com.stevejrong.waiter.business.code.component.ErrorCodes;
import com.stevejrong.waiter.common.component.BaseConstants;
import com.stevejrong.waiter.common.component.util.DateTimeUtil;

import reactor.core.publisher.Mono;

/**
 * 切面 - 认证授权切面
 * <p>
 * 从加载了用户角色权限关联关系的本地JVM缓存中（此数据，源于消息队列的广播消息）查找并判断：用户的请求URI是仅需认证，还是需要授权。
 * <ul>
 * <li>若仅需认证，则只需在成功验证过用户身份后，即可放行。</li>
 * <li>若需要授权，则需先认证用户身份，再查询是否有此URI的授权。若查到此用户对于此URI的访问权限，则放行，否则不放行。</li>
 * </ul>
 * 
 * @author Steve Jrong
 * @since 1.0
 */
@Component
@Aspect
public class AuthcAspect {

	/**
	 * 需要进行认证授权的、方法级别的切点
	 * <p>
	 * 需在宿主项目中添加以下配置项，以使值对应包下的Web控制器，均被授权认证组件拦截并执行身份认证或授权（如有需要）：
	 * <pre>waiter.auth-component.auth-scan-package-expression</pre>
	 * 身份认证或授权成功后，将转至对应的Web控制器，正常执行后续业务逻辑。
	 */
	@Pointcut("${waiter.aa-component.authc-scan-package-expression}")
	private void needAuthc() {
	}

	/**
	 * 无需进行认证授权的、方法级别的切点
	 * <p>
	 * 当某个Web控制器内的方法无需认证或授权时，在其方法上标注{@linkplain com.stevejrong.waiter.auth.component.infrastructure.annotation.NeedNotAuth 无需认证或授权的注解}，
	 * 授权认证组件将不在拦截此方法，直接放行到对应的Web控制器，正常执行后续业务逻辑。
	 */
	@Pointcut("@annotation(com.stevejrong.waiter.aa.component.infrastructure.annotation.NeedNotAuthc)")
	private void needNotAuthc() {
	}

	/**
	 * 切面执行逻辑
	 * 
	 * @param joinPoint
	 * @return
	 * @exception RuntimeException 请求头中无AccessToken
	 * @exception RuntimeException AccessToken的格式非法
	 */
	@Around("needAuthc() && !needNotAuthc()")
	public Mono<Object> aroundExecute(ProceedingJoinPoint joinPoint) {
		Mono<ServerHttpRequest> request = ReactiveHttpExchangeUtil.getReactiveHttpRequest();
		return request.map(req -> {
			// 从请求头中携带的BearerToken，获取AccessToken
			String accessToken = Splitter.on(BaseConstants.SymbolConstants.SYMBOL_SPACE.desc())
					.splitToList(req.getHeaders().get(BaseConstants.HttpHeaderConstants.AUTHORIZATION_HEADER_KEY.desc()).toString()).getLast();
			
	        if (StringUtils.isBlank(accessToken)) {
	        	// 请求头中无AccessToken
	            throw new RuntimeException(ErrorCodes.AuthcErrorCodes.ACCESS_TOKEN_IS_NOT_FOUND_IN_REQUEST_HEADER.desc());
	        }
	        
	        boolean verifySignatureResult = false;
	        JWSObject decodedJwsObject = null;
	        try {
	        	// JWT字串验签
	        	verifySignatureResult = this.verifyJwsObjectSignature(getRsaJwsVerifier(), decodedJwsObject = decodeJWTStringToJwsObject(accessToken));
			} catch (Exception e) {
				// JWT格式错误
				throw new RuntimeException(ErrorCodes.AuthcErrorCodes.ACCESS_TOKEN_IS_INVALID.desc(), e);
			}
			if (!verifySignatureResult) {
				// JWT格式错误
				throw new RuntimeException(ErrorCodes.AuthcErrorCodes.ACCESS_TOKEN_IS_INVALID.desc());
			}

			if (!verifyJwsObjectExpiryDate(decodedJwsObject)) {
				// JWT已过期
				throw new RuntimeException(ErrorCodes.AuthcErrorCodes.ACCESS_TOKEN_IS_EXPIRED.desc());
			}
	        
			Object proceed = null;
			try {
				proceed = (Mono<?>) joinPoint.proceed();
			} catch (Throwable e) {
			}
			return proceed;
		});
	}
	
	/**
	 * 获取面向JWT的RSA算法的密钥验证器
	 * 
	 * @return 面向JWT的RSA算法的密钥验证器对象
	 * @exception RuntimeException RSA密钥验证失败
	 */
	private JWSVerifier getRsaJwsVerifier() {
		RSAKey rsaKey = RSAUtil.getRSAKey(RSAUtil.getPublicKey(null, null), RSAUtil.getPrivateKey(null, null));
        JWSVerifier verifier;
        try {
        	// 使用公钥创建验证器。因为私钥负责签发JWT时的签名，而公钥在用户的登录请求过来时负责验证
            verifier = new RSASSAVerifier(rsaKey.toPublicJWK());
        } catch (JOSEException e) {
            throw new RuntimeException(ErrorCodes.AuthcErrorCodes.RSA_KEY_VERIFY_FAILED.desc(), e);
        }
        
        return verifier;
	}
	
	/**
	 * 解析JWT字串为jwsObject对象
	 * 
	 * @param jwtString JWT字串
	 * @return 由JWT字串解析后的jwsObject对象
	 * @exception RuntimeException JWT解析失败
	 */
	private JWSObject decodeJWTStringToJwsObject(String jwtString) {
		JWSObject jwsObject;
        try {
            jwsObject = JWSObject.parse(jwtString);
        } catch (ParseException e) {
            throw new RuntimeException(ErrorCodes.AuthcErrorCodes.JWT_DECODE_FAILED.desc(), e);
        }
        
        return jwsObject;
	}
	
	/**
	 * 验证JWT对象的签名是否合法
	 * 
	 * @param rsaJwsVerifier 面向JWT的RSA算法的密钥验证器对象
	 * @param decodedJwsObject 由JWT字串解析后的jwsObject对象
	 * @return true - JWT字串（对象）的签名符合JWT规范；false - JWT字串（对象）的签名不符合JWT规范
	 * @exception RuntimeException RSA密钥验证失败
	 */
	private boolean verifyJwsObjectSignature(JWSVerifier rsaJwsVerifier, JWSObject decodedJwsObject) {
		try {
			return decodedJwsObject.verify(rsaJwsVerifier);
		} catch (JOSEException e) {
			throw new RuntimeException(ErrorCodes.AuthcErrorCodes.RSA_KEY_VERIFY_FAILED.desc(), e);
		}
	}
	
	/**
	 * 验证JWT对象是否过期
	 * 
	 * @param decodedJwsObject
	 * @return true - JWT字串（对象）未过期；false - JWT字串（对象）已过期
	 */
	private boolean verifyJwsObjectExpiryDate(JWSObject decodedJwsObject) {
		// 取载荷
		Map<String, Object> payloadMap = decodedJwsObject.getPayload().toJSONObject();
		
		long expAt = Long.parseLong(payloadMap.get("expAt").toString());
		long now = DateTimeUtil.getTimestampByNow();

		/*
		 * 判断逻辑： 当当前时间的时间戳，还没JWT（AccessToken）中的过期时间大时，说明当前时间还未过它的过期时间点，在有效期内。
		 * 当当前时间的时间戳，已经比JWT（AccessToken）中的过期时间大了，这就说明当前时间已经超过了它的过期时间点，判定为过期。
		 */
		if (now >= expAt) {
			return false;
		}
		
		return true;
	}
}